/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */
//package org.apache.batik.parser;

//import java.io.IOException;

/**
 * This class implements an event-based parser for the SVG path's d
 * attribute values.
 *
 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
 * @version $Id: PathParser.java 502167 2007-02-01 09:26:51Z dvholten $
 */

/**
 * This code was adapted to Objective C from the Apache Java code of the same name.
 * The code will parse the path string of a SVG document. A handler is implemented
 * by the user according to the PathHandler interface (protocol in Objective C) that
 * will create the appropriate objects as the path is parsed. This is an event driven
 * parser (e.g. no structure of the parse is saved), and no structure of the parse
 * constructed or saved by this routine. This is useful in cases when one is only
 * reading the path and not modifying it for future writ out, like Inkscape would
 * need to do. For creating game levels from SVG drawings, it works just fine..
 *
 * October 17, 2009
 * @author <a href="mailto:chuck@kollar.com">Charles Kollar</a>
 */

#import "PathParser.h"

@implementation PathParser

/**
 * Allows an application to register a path handler.
 *
 * <p>If the application does not register a handler, all
 * events reported by the parser will be silently ignored.
 *
 * <p>Applications may register a new or different handler in the
 * middle of a parse, and the parser must begin using the new
 * handler immediately.</p>
 * @param handler The transform list handler.
 */
@synthesize pathHandler;

@synthesize stringTokens;
@synthesize tokenI;
@synthesize current;
@synthesize mWasParsed;


/*
 * Break the string into SCG data tokens.
 *
 * Tokens are broken by spaces, or going from a numeral to a character
 * can cause a break. For example 1.04,54.9C.
 *
 * This algorithm is tooo complex and should be simplified...
 */
-(NSArray *)breakStringIntoTokens: (NSString *)st {
    NSMutableArray *tokens = [[NSMutableArray alloc] init];
    NSCharacterSet *scanSet =
        [NSCharacterSet characterSetWithCharactersInString:@"zZmMlLhHvVcCqQsStTaA"];
    NSCharacterSet *stopSet =
        [NSCharacterSet characterSetWithCharactersInString:@" zZmMlLhHvVcCqQsStTaA"];
    NSScanner *theScanner = [NSScanner scannerWithString:st];
    NSString *tokenStr;
    
    while ([theScanner isAtEnd] == NO) {
        // Scan a command character...
        if ([theScanner scanCharactersFromSet:scanSet intoString:&tokenStr]) {
            if ([tokenStr length] == 1) {
                [tokens addObject:tokenStr];
                //NSLog(@"Command: %s", [tokenStr UTF8String]);
            } else {
                //should throw an exception...
                break;
            }
            NSUInteger i = [theScanner scanLocation];
            while ([theScanner isAtEnd] == NO && [st characterAtIndex: i] == ' ') {
                // Skip the spaces following the command
                [theScanner setScanLocation: ++i];
            }
        }
        // Here we should check that there was some data at all
        while ([theScanner isAtEnd] == NO) {
            // Scan as much data as we can for the current command...
            if ([theScanner scanUpToCharactersFromSet:stopSet intoString:&tokenStr]) {
                // we have characters up to the "stopSet" == breakSet
                [tokens addObject:tokenStr];
                //NSLog(@"Data: %s", [tokenStr UTF8String]);
                NSUInteger i = [theScanner scanLocation];
                while ([theScanner isAtEnd] == NO && [st characterAtIndex: i] == ' ') {
                    // Skip the spaces following the command
                    [theScanner setScanLocation: ++i];
                }
                // if theScanner is not pointing at a space then we should be pointing
                // at the next SCG data command (e.g. there was not space between the
                // point and the command), or we are done.
                if ([theScanner isAtEnd] == YES || [st characterAtIndex: i] != ' ') break;
                // skip the space and continue to parse data for the same command.
                [theScanner setScanLocation: ++i];
            }
        }
    }
    [tokens release];
    return (NSArray *)tokens;
}

/**
 * Creates a new PathParser for parsing the DATA portion of an SVG drawing.
 * The method 'doParse' is used to do the parsing. The PathHandler is a
 * protocol that must be implemented to plot the various objects detected
 * by the parser.
 */
-(id)initWithPathHandler:(id<PathHandler>)ph data:(NSString *)dat {
    if (self = [super init]) {
        [self setPathHandler: ph];
        // Tokens are broken by spaces, or going from a numeral to a character
        // can cause a break. For example 1.04,54.9C. So we need something better
        // than this...
        [self setStringTokens: [dat componentsSeparatedByString: @" "]];
        //[self setStringTokens: [self breakStringIntoTokens: dat]];
        [self setTokenI: 0];
        [self setMWasParsed: NO];
    }
    return self;
}

/**
 * Frees the memeory associated with a PathParser.
 */
-(void)dealloc {
    [stringTokens release];
    
    [super dealloc];
}

/*
 * Here we assume that there is a next token, if not then raise an exception.
 */
-(void)nextTokenStringToCurrent {
    // make sure that tokenI is a valid index
    if (tokenI > [stringTokens count]) {
        NSException *ex = 
        [[NSException alloc]
         initWithName: EXCEPTION_RMTTA
         reason: @"A request was made to read a token who's index is greater than the number of tokens available from parsing the path string"
         userInfo: nil];
        [ex raise];
    }
    current = (NSString *)[stringTokens objectAtIndex:tokenI++];
}

/*
 * The next token should contain two comma separated floating point numbers.
 * Note: scannf seems to be able to handle the case where a digit sequence
 * without a decimal point somewhere in it is treated as a float (unlike
 * [NSString -floatVal]
 */
-(void) parse2TupleFromCurrentX: (float *)x Y:(float *)y {
    int scanned;
    scanned = sscanf([current UTF8String], "%f,%f", x, y);
    if (scanned != 2) {
        NSException *ex = 
        [[NSException alloc]
         initWithName: EXCEPTION_ETCSF
         reason:@"An unsuccessful attempt was made to parse the content of a token for two floating point numbers separated by a comma."
         userInfo:nil];
        [ex raise];
    }
}

/*
 * The next token should contain one floating point number.
 */
-(void) parse1TupleFromCurrentX: (float *)x {
    int scanned;
    scanned = sscanf([current UTF8String], "%f", x);
    if (scanned != 1) {
        NSException *ex = 
        [[NSException alloc]
         initWithName: EXCEPTION_EOF
         reason:@"An unsuccessful attempt was made to parse the content of a token for one floating point number."
         userInfo:nil];
        [ex raise];
    }
}

/*
 * The SVG PATH specification allows for the continuation of the current command without
 * an interveining command character. For full details, please see the published works:
 * http://www.w3.org/TR/2003/REC-SVG11-20030114/paths.html#PathData
 * e.g. The command letter can be eliminated on subsequent commands if the same command
 * is used multiple times in a row.
 *
 * This code determines whether this is the case with the current command.
 */
-(BOOL) continueWithTheCurrentCommand {
    [self continueWithTheCurrentCommandMoveingNextTokenStringToCurrent: YES];
}
-(BOOL) continueWithTheCurrentCommandMoveingNextTokenStringToCurrent: (BOOL)next {
    if (tokenI >= [stringTokens count]) return NO;
    // take a peak ahead...
    NSString *nextField = (NSString *)[stringTokens objectAtIndex:tokenI];
    // Does the string of the NEXTFIELD begin with a character that would be found in a number?
    // p.s. I tried looking for '[nextField floatValue] == 0' but this fails when
    // the first number contains NO '.'
    int c = (int)[nextField characterAtIndex:0];
    if (!(isdigit(c) || c == '-' || c== '+')) return NO;
    if (next == YES) [self nextTokenStringToCurrent];
    
    return YES;
}

/*
 http://www.w3.org/TR/2003/REC-SVG11-20030114/paths.html#PathData
 
 Draw a line from the current point to the given (x,y) coordinate which
 becomes the new current point. L (uppercase) indicates that absolute
 coordinates will follow; l (lowercase) indicates that relative coordinates
 will follow. A number of coordinates pairs may be specified to draw a
 polyline. At the end of the command, the new current point is set to the
 final set of coordinates provided.
 
 parameters (y,x)+
 */

/**
 * Parses a 'l' command.
 */
-(void) parsel /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler linetoRelX: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'L' command.
 */
-(void) parseL /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler linetoAbsX: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}


/*
  http://www.w3.org/TR/2003/REC-SVG11-20030114/paths.html#PathData
 
 Start a new sub-path at the given (x,y) coordinate. M (uppercase) indicates
 that absolute coordinates will follow; m (lowercase) indicates that relative
 coordinates will follow.
 
 If a relative moveto (m) appears as the first element
 of the path, then it is treated as a pair of absolute coordinates.
 
 IMPORTANT NOTE: If a moveto is followed by multiple pairs of coordinates, the
 subsequent pairs are treated as implicit lineto commands.
 
 parameters (x,y)+
 */

/**
 * Parses a 'm' command.
 */
-(void) parsem /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    
    float x, y;
    [self parse2TupleFromCurrentX: &x Y: &y];
    
    if ([self mWasParsed] == NO) {
        // If a relative moveto (m) appears as the first element
        // of the path, then it is treated as a pair of absolute coordinates.
        [pathHandler movetoAbsX: x Y: y];
    } else {
        [pathHandler movetoRelX: x Y: y];
    }
    [self setMWasParsed: YES];
    
    // see the IMPORTANT NOTE: above...
    if ([self continueWithTheCurrentCommandMoveingNextTokenStringToCurrent: NO]) [self parsel];
}

/**
 * Parses a 'M' command.
 */
-(void) parseM /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    
    float x, y;
    [self parse2TupleFromCurrentX: &x Y: &y];
    
    [pathHandler movetoAbsX: x Y: y];
    [self setMWasParsed: YES];
    
    // see the IMPORTANT NOTE: above...
    // After checking for continuation of the command, don't move the next
    // token because parseL will do that.
    if ([self continueWithTheCurrentCommandMoveingNextTokenStringToCurrent: NO]) [self parseL];
}


/*
  http://www.w3.org/TR/2003/REC-SVG11-20030114/paths.html#PathData
 
 Draws a horizontal line from the current point (cpx, cpy) to (x, cpy).
 H (uppercase) indicates that absolute coordinates will follow; h 
 (lowercase) indicates that relative coordinates will follow.
 
 Multiple x values can be provided (although usually this doesn't make sense).
 
 At the end of the command, the new current point becomes (x, cpy) for
 the final value of x.
 
 parameters (x)+
 */

/**
 * Parses a 'h' command.
 */
-(void) parseh /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x;
        [self parse1TupleFromCurrentX: &x];
        
        [pathHandler linetoHorizontalRelX:x];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'H' command.
 */
-(void) parseH /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x;
        [self parse1TupleFromCurrentX: &x];
        
        [pathHandler linetoHorizontalAbsX:x];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}


/*
 Draws a vertical line from the current point (cpx, cpy) to (cpx, y).
 V (uppercase) indicates that absolute coordinates will follow; v
 (lowercase) indicates that relative coordinates will follow.
 
 Multiple y values can be provided (although usually this doesn't make sense).
 
 At the end of the command, the new current point becomes (cpx, y) for
 the final value of y.
 
 parameters (y)+
 */

/**
 * Parses a 'v' command.
 */
-(void) parsev /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float y;
        [self parse1TupleFromCurrentX: &y];
        
        [pathHandler linetoVerticalRelY: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'V' command.
 */
-(void) parseV /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float y;
        [self parse1TupleFromCurrentX: &y];
        
        [pathHandler linetoVerticalAbsY: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}


/*
 The curve commands
 */

/*
 Draws a cubic Bézier curve from the current point to (x,y) using (x1,y1) as
 the control point at the beginning of the curve and (x2,y2) as the control
 point at the end of the curve. C (uppercase) indicates that absolute
 coordinates will follow; c (lowercase) indicates that relative coordinates
 will follow. Multiple sets of coordinates may be specified to draw a
 polybézier. At the end of the command, the new current point becomes the
 final (x,y) coordinate pair used in the polybézier.
 
 parameters (x1,y1 x2,y2 x,y)+
 */


/**
 * Parses a 'c' command.
 */
-(void) parsec /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x1, y1;
        [self parse2TupleFromCurrentX: &x1 Y: &y1];
        
        [self nextTokenStringToCurrent];
        float x2, y2;
        [self parse2TupleFromCurrentX: &x2 Y: &y2];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoCubicRelX1: x1 Y1: y1 X2: x2 Y2: y2 X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'C' command.
 */
-(void) parseC /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x1, y1;
        [self parse2TupleFromCurrentX: &x1 Y: &y1];
        
        [self nextTokenStringToCurrent];
        float x2, y2;
        [self parse2TupleFromCurrentX: &x2 Y: &y2];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoCubicAbsX1: x1 Y1: y1 X2: x2 Y2: y2 X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/*
 Draws a cubic Bézier curve from the current point to (x,y). The first control
 point is assumed to be the reflection of the second control point on the
 previous command relative to the current point. (If there is no previous
 command or if the previous command was not an C, c, S or s, assume the first
 control point is coincident with the current point.) (x2,y2) is the second
 control point (i.e., the control point at the end of the curve).
 S (uppercase) indicates that absolute coordinates will follow;
 s (lowercase) indicates that relative coordinates will follow.
 Multiple sets of coordinates may be specified to draw a polybézier. At the
 end of the command, the new current point becomes the final (x,y) coordinate
 pair used in the polybézier.
 
 parameters (x2,y2 x,y)+
 */

/**
 * Parses a 's' command.
 */
-(void) parses /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x2, y2;
        [self parse2TupleFromCurrentX: &x2 Y: &y2];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoCubicSmoothRelX2: x2 Y2: y2 X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'S' command.
 */
-(void) parseS /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x2, y2;
        [self parse2TupleFromCurrentX: &x2 Y: &y2];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoCubicSmoothAbsX2: x2 Y2: y2 X: x Y: y];

        if (![self continueWithTheCurrentCommand]) return;
    }
}

/*
 Draws a quadratic Bézier curve from the current point to (x,y) using (x1,y1)
 as the control point. Q (uppercase) indicates that absolute coordinates will
 follow; q (lowercase) indicates that relative coordinates will follow.
 Multiple sets of coordinates may be specified to draw a polybézier. At the
 end of the command, the new current point becomes the final (x,y) coordinate
 pair used in the polybézier.
 
 parameters (x1,y1 x,y)+
 */

/**
 * Parses a 'q' command.
 */
-(void) parseq /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x1, y1;
        [self parse2TupleFromCurrentX: &x1 Y: &y1];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoQuadraticRelX1: x1 Y1: y1 X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'Q' command.
 */
-(void) parseQ /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x1, y1;
        [self parse2TupleFromCurrentX: &x1 Y: &y1];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoQuadraticAbsX1: x1 Y1: y1 X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/*
 Draws a quadratic Bézier curve from the current point to (x,y). The control
 point is assumed to be the reflection of the control point on the previous
 command relative to the current point. (If there is no previous command or
 if the previous command was not a Q, q, T or t, assume the control point is 
 coincident with the current point.) T (uppercase) indicates that absolute
 coordinates will follow; t  (lowercase) indicates that relative coordinates
 will follow. At the end of the command, the new current point becomes the
 final (x,y) coordinate pair used in the polybézier.
 
 parameters (x,y)+
 */

/**
 * Parses a 't' command.
 */
-(void) parset /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoQuadraticSmoothRelX: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'T' command.
 */
-(void) parseT /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler curvetoQuadraticSmoothAbsX: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/*
 Draws an elliptical arc from the current point to (x, y). The size and
 orientation of the ellipse are defined by two radii (rx, ry) and an
 x-axis-rotation, which indicates how the ellipse as a whole is rotated
 relative to the current coordinate system. The center (cx, cy) of the
 ellipse is calculated automatically to satisfy the constraints imposed
 by the other parameters. large-arc-flag and sweep-flag contribute to
 the automatic calculations and help determine how the arc is drawn.
 
 parameters (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
 */

/**
 * Parses a 'a' command.
 */
-(void) parsea /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        float rx, ry;
        [self parse2TupleFromCurrentX: &rx Y: &ry];
        
        // notexactly sure how Inkscape does this...
        [self nextTokenStringToCurrent];
        float xar;
        [self parse1TupleFromCurrentX: &xar];
        
        [self nextTokenStringToCurrent];
        float laf;
        [self parse1TupleFromCurrentX: &laf];
        
        [self nextTokenStringToCurrent];
        float sf;
        [self parse1TupleFromCurrentX: &sf];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler arcRelRx: rx Ry: ry XAxisRotation: xar LargeArcFlag: laf SweepFlag: sf X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

/**
 * Parses a 'A' command.
 */
-(void) parseA /* throws ParseException, IOException */ {
    [self nextTokenStringToCurrent];
    while (YES) {
        [self nextTokenStringToCurrent];
        float rx, ry;
        [self parse2TupleFromCurrentX: &rx Y: &ry];
        
        // notexactly sure how Inkscape does this...
        [self nextTokenStringToCurrent];
        float xar;
        [self parse1TupleFromCurrentX: &xar];
        
        [self nextTokenStringToCurrent];
        float laf;
        [self parse1TupleFromCurrentX: &laf];
        
        [self nextTokenStringToCurrent];
        float sf;
        [self parse1TupleFromCurrentX: &sf];
        
        [self nextTokenStringToCurrent];
        float x, y;
        [self parse2TupleFromCurrentX: &x Y: &y];
        
        [pathHandler arcAbsRx: rx Ry: ry XAxisRotation: xar LargeArcFlag: laf SweepFlag: sf X: x Y: y];
        
        if (![self continueWithTheCurrentCommand]) return;
    }
}

// Note: rather than implementing "skip subpath" on error
// Here we just give up.

-(void) doParse /* throws ParseException, IOException */ {
    [pathHandler startPath];
    
    while (tokenI < [stringTokens count]) {
        [self nextTokenStringToCurrent];
        //char *localCurrent = [current UTF8String];
        //NSLog(@"nextToken '%s' firstChar 0x%2x", [current UTF8String], [current characterAtIndex:0]);
        int c = (int)[current characterAtIndex:0];
        switch (c) {
            case 0xD:
            case 0xA:
            case 0x20:
            case 0x9:
                [self nextTokenStringToCurrent];
                break;
            case 'z':
            case 'Z':
                [self nextTokenStringToCurrent];
                [pathHandler closePath];
                // Any form of 'z' ends the current path, so we must look
                // for signs of a new path...
                [self setMWasParsed: NO];
                break;
            case 'm': [self parsem]; break;
            case 'M': [self parseM]; break;
            case 'l': [self parsel]; break;
            case 'L': [self parseL]; break;
            case 'h': [self parseh]; break;
            case 'H': [self parseH]; break;
            case 'v': [self parsev]; break;
            case 'V': [self parseV]; break;
            case 'c': [self parsec]; break;
            case 'C': [self parseC]; break;
            case 'q': [self parseq]; break;
            case 'Q': [self parseQ]; break;
            case 's': [self parses]; break;
            case 'S': [self parseS]; break;
            case 't': [self parset]; break;
            case 'T': [self parseT]; break;
            case 'a': [self parsea]; break;
            case 'A': [self parseA]; break;
            default:
                ;NSException *ex = 
                [[NSException alloc]
                 initWithName: EXCEPTION_EASPC
                 reason:@"An unsuccessful attempt was made to parse a path command from a token."
                 userInfo:nil];
                [ex raise];
                break;
        }
    }
    
    if (tokenI != [stringTokens count]) {
        NSException *ex = 
        [[NSException alloc]
         initWithName: EXCEPTION_EOTE
         reason:@"After finishing parsing the tokens not all were processed"
         userInfo:nil];
        [ex raise];
    }
    
    [pathHandler endPath];
}

@end
